#include "precomp.h"

namespace Tmpl {

static HANDLE gExitEvent;
static HANDLE gFinishedEvent;

static HANDLE gJobSemaphore;

static HANDLE* gThreadHandles = 0;
static int gThreadCount = 0;

static CRITICAL_SECTION gJobCS;

// forward decleration of the GetJobByHandle
class Job;
static Job* GetJobByHandle( JobHandle handle );
// ===================================================================================
// Job class implementation
// ===================================================================================
class Job
{
public:
	enum JobStatus 
	{ 
		JOB_ALLOCATED=0,
		JOB_ADDED=1,
		JOB_RUNNING=2
	};

	enum JobFuncType
	{
		JOB_FUNC_NONE,
		JOB_FUNC,
		JOB_FUNC_INT,
		JOB_FUNC_INT_INT,
		JOB_FUNC_INT_INT_INT,
		JOB_FUNC_POINTER_INT,
		JOB_FUNC_POINTER_INT_INT,
		JOB_FUNC_POINTER_POINTER_INT_INT
	};

	inline Job( JobHandle handle )
	{
		mId = handle;
		mStatus = JOB_ALLOCATED; 

		mFuncType = JOB_FUNC_NONE;
		mThreadFunc = 0;
		mData1 = 0;
		mData2 = 0;
		mI1 = 0;
		mI2 = 0;
		mI3 = 0;
	}
	void SetFunc(void(*threadfunc)())
	{
		mFuncType = JOB_FUNC;
		mThreadFunc = threadfunc;
	}
	void SetFunc(void(*threadfunc)(int), int i1)
	{
		mFuncType = JOB_FUNC_INT;
		mThreadFunci = threadfunc;
		mI1 = i1;
	}
	void SetFunc(void(*threadfunc)(int, int), int i1, int i2)
	{
		mFuncType = JOB_FUNC_INT_INT;
		mThreadFuncii = threadfunc;
		mI1 = i1;
		mI2 = i2;
	}
	void SetFunc(void(*threadfunc)(int, int, int), int i1, int i2, int i3)
	{
		mFuncType = JOB_FUNC_INT_INT_INT;
		mThreadFunciii = threadfunc;
		mI1 = i1;
		mI2 = i2;
		mI3 = i3;
	}
	void SetFunc(void(*threadfunc)(void*, int), void* data, int i1)
	{
		mFuncType = JOB_FUNC_POINTER_INT;
		mThreadFuncpi = threadfunc;
		mData1 = data;
		mI1 = i1;
	}
	void SetFunc(void(*threadfunc)(void*, int, int), void* data, int i1, int i2)
	{
		mFuncType = JOB_FUNC_POINTER_INT_INT;
		mThreadFuncpii = threadfunc;
		mData1 = data;
		mI1 = i1;
		mI2 = i2;
	}
	void SetFunc(void(*threadfunc)(void*, void*, int, int), void* data1, void* data2, int i1, int i2)
	{
		mFuncType = JOB_FUNC_POINTER_POINTER_INT_INT;
		mThreadFuncppii = threadfunc;
		mData1 = data1;
		mData2 = data2;
		mI1 = i1;
		mI2 = i2;
	}

	void Run()
	{
		switch (mFuncType)
		{
		case JOB_FUNC							: mThreadFunc(); break; 
		case JOB_FUNC_INT						: mThreadFunci(mI1); break; 
		case JOB_FUNC_INT_INT					: mThreadFuncii(mI1, mI2); break; 
		case JOB_FUNC_INT_INT_INT				: mThreadFunciii(mI1, mI2, mI3); break; 
		case JOB_FUNC_POINTER_INT				: mThreadFuncpi(mData1, mI1); break; 
		case JOB_FUNC_POINTER_INT_INT			: mThreadFuncpii(mData1, mI1, mI2); break; 
		case JOB_FUNC_POINTER_POINTER_INT_INT	: mThreadFuncppii(mData1, mData2, mI1, mI2); break; 
		};
	}

	bool IsAllDependenciesDone()
	{
		std::vector<JobHandle>::iterator it;
		for (it=mDependencies.begin(); it!=mDependencies.end(); ++it)
		{
			Job* j = GetJobByHandle(*it); 
			if (j)
			{
				return false;
			}
		}
		return true;
	}

	void AddDependency( JobHandle id )
	{ 
		mDependencies.push_back(id);
	}

	JobHandle GetHandle()	{return mId;}
	bool IsAllocated()		{return mStatus==JOB_ALLOCATED;}
	bool IsAdded()			{return mStatus==JOB_ADDED;}
	bool IsExecuting()		{return mStatus==JOB_RUNNING;}
	void SetStatus( JobStatus status ){mStatus = status;}

private:
	JobHandle mId;
	JobStatus mStatus;

	std::vector<JobHandle> mDependencies;

	char mFuncType;

	union
	{
		void(*mThreadFunc)();
		void(*mThreadFunci)(int);
		void(*mThreadFuncii)(int, int);
		void(*mThreadFunciii)(int, int, int);
		void(*mThreadFuncpi)(void*, int);
		void(*mThreadFuncpii)(void*, int, int);
		void(*mThreadFuncppii)(void*, void*, int, int);
	};

	void* mData1;
	void* mData2;
	int mI1;
	int mI2;
	int mI3;

};

// ===================================================================================
// JobManager class implementation
// ===================================================================================
typedef std::map<JobHandle, Job*> JobPool;
static JobPool gJobs;
static int gNextJobId = 1;

// JobThreads
static Job* GetJobByHandle( JobHandle handle )
{
	JobPool::iterator it = gJobs.find(handle);
	if(it != gJobs.end()) return it->second;
	return 0;
}
static DWORD WINAPI JobManagerJobRunner( LPVOID param )
{
	HANDLE handles[2];
	handles[0] = gExitEvent;
	handles[1] = gJobSemaphore;
	while(1)
	{
		switch (WaitForMultipleObjects(2, handles, FALSE, INFINITE))
		{
		// Exit Event
		case WAIT_OBJECT_0:
			return TRUE;

		// Semaphore
		case WAIT_OBJECT_0+1:
			break;

		// Timeout
		case WAIT_TIMEOUT:
			break;

		// Error
		default:
			Log::Write("JobManagerJobRunner - Error Occured!");
			break;
		}

		// Find new Job
		Job* job = 0;
		EnterCriticalSection(&gJobCS);
		for (JobPool::iterator it=gJobs.begin(); it!=gJobs.end(); ++it)
		{
			if (it->second->IsAdded())
			{
				if (it->second->IsAllDependenciesDone())
				{
					job = it->second;
					job->SetStatus(Job::JOB_RUNNING);
					break;
				}
			}
		}
		LeaveCriticalSection(&gJobCS);

		// If we have a job execute it
		if (!job) continue;
		job->Run();

		EnterCriticalSection(&gJobCS);

		// Remove Job
		gJobs.erase(job->GetHandle());
		delete job;

		// Flag Done
		if (gJobs.size() == 0)
		{
			SetEvent(gFinishedEvent);
		}

		LeaveCriticalSection(&gJobCS);

		// We can processa a new job
		ReleaseSemaphore( gJobSemaphore, gThreadCount, NULL );
	}
	return TRUE;
}

// JobManager
void JobManager::Init( int threadcount )
{
	if (threadcount <= 0)
	{
		Log::Write("JobManager::Init - threadcount value must be greater then zero.");
		return;
	}

	InitializeCriticalSection( &gJobCS );

	// Create Handles
	gExitEvent = CreateEvent( NULL, TRUE, FALSE, NULL);
	gFinishedEvent = CreateEvent( NULL, TRUE, TRUE, NULL);
	gJobSemaphore = CreateSemaphore( NULL, 0, 10000, NULL );

	// Create Threads
	gThreadCount = threadcount;
	gThreadHandles = new HANDLE[gThreadCount];
	for (int i=0; i<gThreadCount; ++i)
	{
		gThreadHandles[i] = CreateThread( NULL, 0, JobManagerJobRunner, NULL, 0, NULL );
	}
}
void JobManager::Exit( )
{
	if (gThreadCount <= 0) return;

	WaitForJobsToFinish();
	SetEvent(gExitEvent);
	WaitForMultipleObjects(gThreadCount, gThreadHandles, TRUE, INFINITE);

	// Delete Threads
	for (int i=0; i<gThreadCount; ++i)
	{
		CloseHandle(gThreadHandles[i]);
	}
	delete gThreadHandles;

	// Delete Handles
	CloseHandle(gExitEvent);
	CloseHandle(gFinishedEvent);
	CloseHandle(gJobSemaphore);

	DeleteCriticalSection( &gJobCS );
}
bool JobManager::IsJobDone( JobHandle handle )
{
	EnterCriticalSection(&gJobCS);
	Job* result = GetJobByHandle(handle);
	LeaveCriticalSection(&gJobCS);
	return (result == 0);
}

void JobManager::WaitForJobsToFinish()
{
	WaitForSingleObject(gFinishedEvent, INFINITE);
}
int JobManager::GetJobCount()
{
	int jobcount = 0;
	EnterCriticalSection(&gJobCS);
	jobcount = (int)gJobs.size();
	LeaveCriticalSection(&gJobCS);
	return jobcount; 
}
int JobManager::GetThreadCount()
{
	return gThreadCount;
}
// Creating Jobs
static Job* CreateNewJob()
{
	ResetEvent( gFinishedEvent );

	EnterCriticalSection(&gJobCS);

	gNextJobId = (gNextJobId & 0xFFFFFFF) + 1;
	Job* job = new Job(gNextJobId);
	gJobs.insert(std::pair<JobHandle,Job*>(gNextJobId, job));

	LeaveCriticalSection(&gJobCS);

	return job;
}
JobHandle JobManager::NewJob( )
{
	Job* job = CreateNewJob();
	return job->GetHandle();
}
JobHandle JobManager::NewJob( void(*threadfunc)() )
{
	Job* job = CreateNewJob();
	if (threadfunc) 
		job->SetFunc( *threadfunc );
	else
		Log::Write("JobManager::NewJob - Empty Function assignment, exepcted Func()!");
	return job->GetHandle();
}
JobHandle JobManager::NewJob( void(*threadfunc)(int), int i1 )
{
	Job* job = CreateNewJob();
	if (threadfunc) 
		job->SetFunc( *threadfunc, i1 );
	else
		Log::Write("JobManager::NewJob - Empty Function assignment, exepcted Func(int)!");
	return job->GetHandle();
}
JobHandle JobManager::NewJob( void(*threadfunc)(int, int), int i1, int i2 )
{
	Job* job = CreateNewJob();
	if (threadfunc) 
		job->SetFunc( *threadfunc, i1, i2 );
	else
		Log::Write("JobManager::NewJob - Empty Function assignment, exepcted Func(int, int)!");
	return job->GetHandle();
}
JobHandle JobManager::NewJob( void(*threadfunc)(int, int, int), int i1, int i2, int i3 )
{
	Job* job = CreateNewJob();
	if (threadfunc) 
		job->SetFunc( *threadfunc, i1, i2, i3 );
	else
		Log::Write("JobManager::NewJob - Empty Function assignment, exepcted Func(int, int, int)!");
	return job->GetHandle();
}
JobHandle JobManager::NewJob( void(*threadfunc)(void*, int), void* data, int i1 )
{
	Job* job = CreateNewJob();
	if (threadfunc) 
		job->SetFunc( *threadfunc, data, i1 );
	else
		Log::Write("JobManager::NewJob - Empty Function assignment, exepcted Func(void*, int)!");
	return job->GetHandle();
}
JobHandle JobManager::NewJob( void(*threadfunc)(void*, int, int), void* data, int i1, int i2 )
{
	Job* job = CreateNewJob();
	if (threadfunc) 
		job->SetFunc( *threadfunc, data, i1, i2 );
	else
		Log::Write("JobManager::NewJob - Empty Function assignment, exepcted Func(void*, int, int)!");
	return job->GetHandle();
}
JobHandle JobManager::NewJob( void(*threadfunc)(void*, void*, int, int), void* data1, void* data2, int i1, int i2 )
{
	Job* job = CreateNewJob();
	if (threadfunc) 
		job->SetFunc( *threadfunc, data1, data2, i1, i2 );
	else
		Log::Write("JobManager::NewJob - Empty Function assignment, exepcted Func(void* void* int int)!");
	return job->GetHandle();
}

void JobManager::AddJobDependency( JobHandle job, JobHandle dependency )
{
	EnterCriticalSection(&gJobCS);
	Job* j = GetJobByHandle(job);
	if (j) 
	{
		if (j->IsAllocated())
		{
			Job* d = GetJobByHandle(dependency);
			if (d) j->AddDependency( dependency );
		}
		else
		{
			Log::Write("JobManager::AddJobDependency - Trying to add a dependency on a job that has already executed!");
		}
	}
	else
	{
		Log::Write("JobManager::AddJobDependency - Trying to add a dependency on an invalid handle!");
	}
	LeaveCriticalSection(&gJobCS);
}
void JobManager::AddExecuteJob( JobHandle job )
{
	EnterCriticalSection(&gJobCS);
	Job* j = GetJobByHandle(job);
	if (j)
	{
		if (j->IsAllocated())
		{
			j->SetStatus(Job::JOB_ADDED);
		}
		else
		{
			Log::Write("JobManager::AddExecuteJob - Trying to add a job that has already executed!");
		}
	}
	else
	{
		Log::Write("JobManager::AddExecuteJob - Trying to add a job with an invalid handle!");
	}
	LeaveCriticalSection(&gJobCS);

	ReleaseSemaphore( gJobSemaphore, 1, NULL );
}
// JobEvent
static void SetAnEventJobEventSet(void* eventHandle, int reset)
{
	if (reset == 0)
		SetEvent(eventHandle);
	else
		ResetEvent(eventHandle);
}
JobHandle JobManager::SetAnEventJob(HANDLE eventHandle, int reset)
{
	return NewJob(&SetAnEventJobEventSet, eventHandle, reset);
}

}; // namespace Relativity